<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      overflow: hidden;
      width: 100vw;
      height: 100vh;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>
    let w = 0
    let h = 0
    let t = 0

    function dist(p1x, p1y, p2x, p2y) {
      return Math.sqrt(Math.pow(p2x - p1x, 2) + Math.pow(p2y - p1y, 2))
    }

    class Canvas {
      constructor() { }

      /**
       * @param {HTMLCanvasElement} canvas
       */
      init(canvas) {
        const cxt = canvas.getContext('2d')
        w = (canvas.width = window.innerWidth)
        h = (canvas.height = window.innerHeight)
        cxt.fillStyle = 'rgb(30, 30, 30)'
        cxt.fillRect(0, 0, w, h)
        return {
          cxt, canvas
        }
      }
    }

    const { cxt, canvas } = new Canvas().init(document.getElementById('canvas'))

    class Segment {
      constructor(parent, l, a, first) {
        this.first = first
        this.pos = first ? {
          x: parent.x,
          y: parent.y
        } : {
          x: parent.nextPos.x,
          y: parent.nextPos.y,
        }
        this.l = l
        this.ang = a
        this.nextPos = {
          x: this.pos.x + this.l * Math.cos(this.ang),
          y: this.pos.y + this.l * Math.sin(this.ang)
        }
      }

      update(t) {
        this.ang = Math.atan2(t.y - this.pos.y, t.x - this.pos.x)
        this.pos = {
          x: t.x + this.l * Math.cos(this.ang - Math.PI),
          y: t.y + this.l * Math.sin(this.ang - Math.PI)
        }
        this.nextPos = {
          x: this.pos.x + this.l * Math.cos(this.ang),
          y: this.pos.y + this.l * Math.sin(this.ang)
        }
      }

      fallback(t) {
        this.pos = t
        this.nextPos = {
          x: this.pos.x + this.l * Math.cos(this.ang),
          y: this.pos.y + this.l * Math.sin(this.ang)
        }
      }

      show() {
        cxt.lineTo(this.nextPos.x, this.nextPos.y)
      }
    }

    class Tentacle {
      constructor(x, y, l, n, a) {
        this.x = x
        this.y = y
        this.l = l
        this.n = n
        this.t = {}
        this.rand = Math.random()
        this.segments = [new Segment(this, l / n, 0, true)]
        for (let i = 1; i < n; i++) {
          this.segments.push(new Segment(this.segments[i - 1], l / n, 0, false))
        }
      }

      move(last_target, target) {
        this.angle = Math.atan2(target.y - this.y, target.x - this.x)
        this.dt = dist(last_target.x, last_target.y, target.x, target.y)
        this.t = {
          x: target.x - 0.8 * this.dt * Math.cos(this.angle),
          y: target.y - 0.8 * this.dt * Math.sin(this.angle)
        }
        if (this.t.x) {
          this.segments[this.n - 1].update(this.t)
        } else {
          this.segments[this.n - 1].update(target)
        }
        for (let i = this.n - 2; i >= 0; i--) {
          this.segments[i].update(this.segments[i + 1].pos)
        }
        if (
          dist(this.x, this.y, target.x, target.y) <=
          this.l + dist(last_target.x, last_target.y, target.x, target.y)
        ) {
          this.segments[0].fallback({ x: this.x, y: this.y })
          for (let i = 1; i < this.n; i++) {
            this.segments[i].fallback(this.segments[i - 1].nextPos)
          }
        }
      }

      show(target) {
        if (dist(this.x, this.y, target.x, target.y) <= this.l) {
          cxt.globalCompositeOperation = 'lighter'
          cxt.beginPath()
          cxt.moveTo(this.x, this.y)
          for (let i = 0; i < this.n; i++) {
            this.segments[i].show()
          }

          cxt.strokeStyle = `hsl(${this.rand * 60 + 180}, 100%, ${this.rand * 60 + 25}%)`

          cxt.lineWidth = this.rand * 2
          cxt.lineCap = 'round'
          cxt.lineJoin = 'round'
          cxt.stroke()
          cxt.globalCompositeOperation = 'source-over'
        }
      }

      show2(target) {
        cxt.beginPath()
        if (dist(this.x, this.y, target.x, target.y) <= this.l) {
          cxt.arc(this.x, this.y, 2 * this.rand + 1, 0, 2 * Math.PI)
          cxt.fillStyle = 'whith'
        } else {
          cxt.arc(this.x, this.y, this.rand * 2, 0, 2 * Math.PI)
          cxt.fillStyle = 'darkcyan'
        }
        cxt.fill()
      }
    }

    const maxl = 300,
      minl = 50,
      n = 30,
      numt = 500,
      tent = [],
      target = { x: 0, y: 0 },
      last_target = {},
      mouse = { x: 0, y: 0 },
      q = 10

    function init() {
      w = (canvas.width = window.innerWidth)
      h = (canvas.height = window.innerHeight)
      for (let i = 0; i < numt; i++) {
        tent.push(
          new Tentacle(
            Math.random() * w,
            Math.random() * h,
            Math.random() * (maxl - minl) + minl,
            n,
            Math.random() * 2 * Math.PI
          )
        )
      }
    }

    init()

    function draw() {
      if (mouse.x) {
        target.errx = mouse.x - target.x
        target.erry = mouse.y - target.y
      } else {
        target.errx =
          w / 2 +
          ((h / 2 - q) *
            Math.sqrt(2) *
            Math.cos(t)) /
          (Math.pow(Math.sin(t), 2) + 1) -
          target.x
        target.erry =
          h /
          2 +
          ((h / 2 - q) *
            Math.sqrt(2) *
            Math.cos(t) *
            Math.sin(t)) /
          (Math.pow(Math.sin(t), 2) + 1) -
          target.y
      }
      target.x += target.errx / 10
      target.y += target.erry / 10

      t += 0.01

      cxt.beginPath()

      cxt.arc(
        target.x,
        target.y,
        dist(last_target.x, last_target.y) + 5,
        0,
        2 * Math.PI
      )
      cxt.fillStyle = 'hsl(210, 100%, 80%)'
      cxt.fill()

      for (let i = 0; i < numt; i++) {
        tent[i].move(last_target, target)
        tent[i].show2(target)
      }

      for (let i = 0; i < numt; i++) {
        tent[i].show(target)
      }
      last_target.x = target.x
      last_target.y = target.y
    }

    function loop() {
      cxt.clearRect(0, 0, w, h)
      draw()
    }

    window.addEventListener('resize', () => {
      w = canvas.width = window.innerWidth
      h = canvas.height = window.innerHeight
      loop()
    })

    loop()

    setInterval(loop, 1000 / 50)

    canvas.addEventListener('mousemove', (e) => {
      last_target.x = mouse.x
      last_target.y = mouse.y
      mouse.x = e.pageX
      mouse.y = e.pageY
    }, false)

    canvas.addEventListener('mouseleave', () => {
      mouse.x = 0
      mouse.y = 0
    })
  </script>
</body>

</html>